#pragma once

#include "error.h"

namespace sfc::io {

template <class I>
struct Read {
  void read_exact(Slice<u8> buf) {
    auto& self = static_cast<I&>(*this);
    while (!buf.is_empty()) {
      auto n = self.read(buf);
      if (n == 0) {
        throw Error{Error::UnexpectedEof};
      }
    }
  }

  auto read_to_end(Vec<u8>& buf, usize buf_size = 64) -> usize {
    const auto old_len = buf.len();
    while (true) {
      buf.reserve(buf_size);
      auto rd_buf = Slice{buf.ptr() + buf.len(), buf.cap() - buf.len()};
      auto rd_cnt = this->read(rd_buf);
      buf._len += rd_cnt;
    }
    return buf.len() - old_len;
  }

  auto read_to_string(String& buf, usize buf_size = 64) -> usize {
    return this->read_to_end(buf, buf_size);
  }
};

template <class R>
struct BufReader : Read<BufReader<R>> {
  R _inn;
  Vec<u8> _buf;

  explicit BufReader(R&& inn, Vec<u8>&& buf = Vec<u8>::with_capacity(1024))
      : _inn{mem::move(inn)}, _buf{mem::move(buf)} {}

  void fill_buf() {
    if (!_buf.is_empty()) {
      return;
    }
    auto cnt = _inn.read({_buf.ptr(), _buf.cap()});
    _buf._len = cnt;
  }

  void consume(usize amt) {
    const auto n = cmp::min(_buf.len(), amt);
    ptr::move(_buf.ptr(), _buf.ptr() + n, _buf.len() - n);
  }

  auto read(Slice<u8> buf) -> usize {
    this->fill_buf();
    const auto n = cmp::min(_buf.len(), buf.len());
    ptr::copy(_buf.ptr(), buf.ptr(), n);
    this->consume(n);
    return n;
  }

  auto read_until(u8 delim, Vec<u8>& buf) -> usize {
    const auto old_len = buf.len();
    while (true) {
      this->fill_buf();
      const auto r = _buf.find(cmp::Eq{delim});
      const auto n = r || _buf.len();
      buf.extend(_buf[{_, n}]);
      this->consume(n);
      if (r) {
        break;
      }
    }
    return buf.len() - old_len;
  }

  auto read_line(String& buf) -> usize {
    return this->read_until(u8('\n'), buf);
  }
};

}  // namespace sfc::io
